///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef __BUOLA_PYTHON_TYPES_YOBJECT_H__
#define __BUOLA_PYTHON_TYPES_YOBJECT_H__

#include <buola/python.h>
#include <buola/python/cobjectbase.h>
#include <buola/io.h>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"

namespace buola { namespace python {

class YAuto;

class YObject
{
public:
    YObject()
        :   mPyObject(nullptr)
    {}

    YObject(PyObject *pPyObject,bool pIncRef=true)
        :   mPyObject(pPyObject)
    {
        if(pIncRef)
            Py_XINCREF(mPyObject);
    }

    YObject(const YObject &pPython)
        :   mPyObject(pPython.mPyObject)
    {
        Py_XINCREF(mPyObject);
    }

    YObject(YObject &&pO)
        :   mPyObject(pO.mPyObject)
    {
        pO.mPyObject=nullptr;
    }

    YObject(CObjectBase *pObject,bool pIncRef=false)
        :   mPyObject(pObject->GetPythonObject())
    {
        if(pIncRef)
            Py_XINCREF(mPyObject);
    }

    explicit YObject(double pValue)
        :   mPyObject(PyFloat_FromDouble(pValue))
    {}

    explicit YObject(int8_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(uint8_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(int16_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(uint16_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(int32_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(uint32_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(int64_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(uint64_t pValue)
        :   mPyObject(PyLong_FromLong(pValue))
    {}

    explicit YObject(bool pValue)
        :   mPyObject(PyBool_FromLong(pValue))
    {}

    explicit YObject(const std::wstring &pString)
        :   mPyObject(PyUnicode_FromWideChar(pString.data(),pString.size()))
    {}

    explicit YObject(const std::string &pString)
        :   mPyObject(PyUnicode_FromStringAndSize(pString.data(),pString.size()))
    {}

    explicit YObject(const wchar_t *pString)
        :   mPyObject(PyUnicode_FromWideChar(pString,-1))
    {}

    explicit YObject(const char *pString)
        :   mPyObject(PyUnicode_FromString(pString))
    {}

    ~YObject()
    {
        Py_XDECREF(mPyObject);
    }

    bool IsModule() const
    {
        return PyModule_Check(mPyObject);
    }

    bool IsType() const
    {
        return PyType_Check(mPyObject);
    }

    bool IsMethod() const
    {
        return PyMethod_Check(mPyObject);
    }

    bool IsCallable() const
    {
        return PyCallable_Check(mPyObject);
    }

    bool IsSequence() const
    {
        return PySequence_Check(mPyObject);
    }
    
    bool IsTuple() const
    {
        return PyTuple_Check(mPyObject);
    }

    bool IsList() const
    {
        return PyList_Check(mPyObject);
    }

    bool IsDict() const
    {
        return PyDict_Check(mPyObject);
    }

    bool IsNull() const
    {
        return (mPyObject==nullptr);
    }

    bool IsNone() const
    {
        return (mPyObject==Py_None);
    }

    bool IsFloat() const
    {
        return PyFloat_Check(mPyObject);
    }

    bool IsBool() const
    {
        return PyBool_Check(mPyObject);
    }

    bool IsInt() const
    {
        return PyLong_Check(mPyObject);
    }

    bool IsNumber() const
    {
        return IsInt()||IsFloat();
    }

    bool IsString() const
    {
        return PyUnicode_Check(mPyObject);
    }

    bool IsBytes() const
    {
        return PyBytes_Check(mPyObject);
    }

    double ToFloat() const
    {
        return PyFloat_AsDouble(mPyObject);
    }

    int64_t ToInt() const
    {
        return PyLong_AsLong(mPyObject);
    }

    std::wstring ToWString() const
    {
        std::wstring lReturn(PyUnicode_GetSize(mPyObject),L'\0');
        PyUnicode_AsWideChar(mPyObject,&lReturn[0],lReturn.size());
        return lReturn;
    }

    std::string ToString() const
    {
        return utf8(ToWString());
    }

    bool ToBool() const
    {
        return (PyObject_IsTrue(mPyObject));
    }

    YObject GetAttribute(const std::string &pName) const
    {
        return YObject(PyObject_GetAttrString(mPyObject,pName.c_str()),false);
    }

    void SetAttribute(const std::string &pName,const YObject &pObject)
    {
        PyObject_SetAttrString(mPyObject,pName.c_str(),pObject);
    }
    
    YList Dir() const;

    YObject GetType() const
    {
        return YObject(PyObject_Type(mPyObject),false);
    }

    std::string GetRepr() const
    {
        YObject lRepr(PyObject_Repr(mPyObject),false);
        return lRepr.ToString();
    }

    std::string GetStr() const
    {
        YObject lStr(PyObject_Str(mPyObject),false);
        return lStr.ToString();
    }

    std::wstring GetWRepr() const
    {
        YObject lRepr(PyObject_Repr(mPyObject),false);
        return lRepr.ToWString();
    }

    std::wstring GetWStr() const
    {
        YObject lStr(PyObject_Str(mPyObject),false);
        return lStr.ToWString();
    }

    template<typename tType>
    bool IsOfType() const
    {
        return PyObject_TypeCheck(mPyObject,tType::mType->mType);
    }

    template<typename tType>
    tType *GetAsType() const
    {
        return throwing_cast<tType*>(CObjectBase::GetBuolaPythonObject(mPyObject));
    }

    template<typename tType>
    tType To();

    operator PyObject*() const
    {
        return mPyObject;
    }

    explicit operator bool() const
    {
        return mPyObject!=nullptr;
    }

    const YObject &operator=(const YObject &pOther)
    {
        if(mPyObject!=pOther.mPyObject)
        {
            Py_XDECREF(mPyObject);
            mPyObject=pOther.mPyObject;
            Py_XINCREF(mPyObject);
        }
        return (*this);
    }

    const YObject &IncRef() const
    {
        Py_XINCREF(mPyObject);
        return (*this);
    }

    const YObject &DecRef() const
    {
        Py_XDECREF(mPyObject);
        return (*this);
    }

    template<typename... tArgs>
    inline YObject CallMethod(const std::string &pName,tArgs&&... pArgs);

    PyObject **GetPointerToPyObject()   {   return &mPyObject;  }
protected:
    PyObject *mPyObject;
};

//NOTE:here only to make editor happy... move into class once kdevelop is fixed
template<typename... tArgs>
inline YObject YObject::CallMethod(const std::string &pName,tArgs&&... pArgs)
{
    PyObject *lRet=PyObject_CallMethodObjArgs(mPyObject,YObject(pName),
                                        (PyObject*)YObject(std::forward<tArgs>(pArgs))...,
                                        nullptr);
    return YObject(lRet,false);
}

inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const YObject &pObject)
{
    pWriter << pObject.GetRepr();
    return pWriter;
}

/*namespace python*/ } /*namespace buola*/ }

#pragma GCC diagnostic pop

#endif
